package cn.bigcontainer.alt.meta.po;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnore;

import cn.bigcontainer.alt.common.entity.BigMap;
import cn.bigcontainer.alt.common.util.WoKit;
import cn.bigcontainer.alt.meta.dto.MObjectDto;
import cn.bigcontainer.alt.meta.exception.MException;
import cn.bigcontainer.alt.meta.mapper.MObjectMapper;
import lombok.extern.slf4j.Slf4j;

/**
 * 对象关系，和数据库外键、关系表映射，不会存在单独的表中，作为json保存在cn.bigcontainer.alt.meta.po.MObject.relations中
 * @author cailei
 * @date 2019-12-04
 */
@Slf4j
public class MRelation implements Comparable<MRelation>{

	/**
	 * 一对多
	 */
	public final static String TYPE_12M = "12M";
	/**
	 * 多对一
	 */
	public final static String TYPE_M21 = "M21";
	/**
	 * 一对多，实际一对一（父对子）
	 */
	public final static String TYPE_1M = "1M";
	/**
	 * 多对一，实际一对一（子对父）
	 */
	public final static String TYPE_M1 = "M1";
	/**
	 * 多对多
	 */
	public final static String TYPE_M2M = "M2M";
	/**
	 * 多对（复合对象：即多个对象）多，例如，email的收件人，有可能是多个用户，或者多个用户组，用户和用户组是不同的对象
	 */
	public final static String TYPE_M2CM = "M2CM";
	/**
	 * 多对（复合对象：即多个对象）一，类似于TYPE_M2CM
	 */
	public final static String TYPE_M2C1 = "M2C1";

	public final static String PARAM_DEFAULT = "default";

	private String id;

	private String name;

	/**
	 * 关系类型，m21/m2m/12m/121
	 */
	private String type;

	/**
	 * 本方属性，例如type=M21，则为外键属性id；type=12M时，为本方对象主键属性id
	 */
	private String propertyIds;

	/**
	 * 本方属性，仅当type=M2C1时，用于存储propertyIds值对应的对象（即，表）
	 */
	private String propertyIdsObjectId;
	
	/**
	 * 被关联对象id，如果类型为M2CM/M2C1时，有可能为多个，以逗号隔开
	 */
	private String relatedObjectId;

	/**
	 * 本方对象名称
	 */
	private String relatedName;

	/**
	 * relatedObject的属性id，12M时，为relatedObject外键属性id；M21/M2M/M2C1/M2CM时，为relatedObject主键属性id。如有多个，以逗号隔开
	 */
	private String relatedIds;

	/**
	 * 多对多关系表对象id
	 */
	private String relationObjectId;

	/**
	 * 多对多关系表对象中，连接本方对象的属性id
	 */
	private String relationIds;

	/**
	 * 多对多关系表对象中，连接他方对象的属性id
	 */
	private String relationInverseIds;

	/**
	 * 当类型为M2CM时，多对多关系表对象中，用于存储复合对象ID的属性id
	 */
	private String relationRelatedObjectId;
	
	private Integer index;

	private BigMap<String, Object> data = new BigMap<>();

	/**
	 * 内存数据，无需序列化
	 */
	@JsonIgnore
	private MObjectDto object;

	@JsonIgnore
	public boolean isM2M() {
		return TYPE_M2M.equals(type);
	}

	@JsonIgnore
	public boolean isM2CM() {
		return TYPE_M2CM.equals(type);
	}
	
	@JsonIgnore
	public boolean isM21() {
		return TYPE_M21.equals(type);
	}
	
	@JsonIgnore
	public boolean isM2C1() {
		return TYPE_M2C1.equals(type);
	}

	@JsonIgnore
	public boolean is12M() {
		return TYPE_12M.equals(type);
	}

	@JsonIgnore
	public boolean isM1() {
		return TYPE_M1.equals(type);
	}

	@JsonIgnore
	public boolean is1M() {
		return TYPE_1M.equals(type);
	}
	
	@JsonIgnore
	public boolean is2C() {
		return type.indexOf("2C") > 0;
	}

	private MObjectDto relatedObject;

	@JsonIgnore
	public MObjectDto getRelatedObject () {
		if (relatedObject == null) {
			relatedObject = new MObjectDto(WoKit.getBean(MObjectMapper.class).selectById(this.relatedObjectId));
		}
		return relatedObject;
	}
	
	@JsonIgnore
	public List<MObjectDto> getRelatedObjects () {
		List<MObject> relatedObjects = WoKit.getBean(MObjectMapper.class).selectBatchIds(Arrays.asList(getRelatedObjectId().split(",")));
		return WoKit.getDtos(MObjectDto.class, relatedObjects);
	}

	private MObjectDto relationObject;

	@JsonIgnore
	public MObjectDto getRelationObject () {
		if (relationObject == null) {
			relationObject = new MObjectDto(WoKit.getBean(MObjectMapper.class).selectById(this.relationObjectId));
		}
		return relationObject;
	}

	/**
	 * 获取本方关联属性
	 *
	 * @return
	 */
	@JsonIgnore
	public List<MProperty> getProperties() {
		List<MProperty> props = new ArrayList<MProperty>();
		String[] ids = this.getPropertyIds().split(",");
		for (String id : ids) {
			MProperty prop = this.getObject().getProperty(id);
			if (prop == null) {
				throw new MException(MException.RELATION_PROPS, this.getId(), id);
			}
			props.add(prop);
		}
		return props;
	}

	/**
	 * 获取被关联方的关联属性
	 *
	 * @return
	 */
	@JsonIgnore
	public List<MProperty> getRelatedProperties() {
		List<MProperty> props = new ArrayList<MProperty>();
		String[] ids = this.getRelatedIds().split(",");
		for (String id : ids) {
			MProperty prop = this.getRelatedObject().getProperty(id);
			if (prop == null) {
				throw new MException(MException.RELATION_RELATEDPROPS, this.getId(), id);
			}
			props.add(prop);
		}
		return props;
	}

	/**
	 * 获取关系对象关联“本方”的关联属性
	 *
	 * @return
	 */
	@JsonIgnore
	public List<MProperty> getRelationProperties() {
		List<MProperty> props = new ArrayList<MProperty>();
		String[] ids = this.getRelationIds().split(",");
		for (String id : ids) {
			MProperty prop = this.getRelationObject().getProperty(id);
			if (prop == null) {
				throw new MException(MException.RELATION_BRIDGEPROPS, this.getId(), id);
			}
			props.add(prop);
		}
		return props;
	}
	
	/**
	 * 获取关系对象关联“本方”的关联属性
	 *
	 * @return
	 */
	@JsonIgnore
	public MProperty getRelationRelatedObjectIdProperty() {
		MProperty prop = this.getRelationObject().getProperty(getRelationRelatedObjectId());
		if (prop == null && TYPE_M2CM.equals(type)) {
			throw new MException(MException.RELATION_BRIDGEPROPS, this.getId(), id);
		}
		return prop;
	}

	/**
	 * 获取关系对象关联“被关联方”的关联属性
	 *
	 * @return
	 */
	@JsonIgnore
	public List<MProperty> getRelationInverseProperties() {
		List<MProperty> props = new ArrayList<MProperty>();
		String[] ids = this.getRelationInverseIds().split(",");
		for (String id : ids) {
			MProperty prop = this.getRelationObject().getProperty(id);
			if (prop == null) {
				throw new MException(MException.RELATION_BRIDGEPROPS, this.getId(), id);
			}
			props.add(prop);
		}
		return props;
	}

	/**
	 * @param props
	 * @param hasTableAlias
	 *            true : (APPS.Id,APPS.NAME) in (('',''),('','')) , false :
	 *            (Id,NAME) in (('',''),('',''))
	 * @param val xxx,yyy,aaa,bbb
	 * @return
	 */
	private static String getPropertiesInCondition(List<MProperty> props, boolean hasTableAlias, String val) {
		StringBuffer sf = new StringBuffer();
		String[] vals = val.split(",");
		int valSize = vals.length / props.size();
		StringBuffer[] sqlVals = new StringBuffer[valSize];
		for (int i = 0; i < props.size(); i++) {
			MProperty p = props.get(i);
			if (i > 0) {
				sf.append(",");
			}
			if (hasTableAlias) {
				sf.append(p.getObject().getNo() + ".");
			}
			sf.append(p.getName());
			for (int j = 0; j < valSize; j ++) {
				if (sqlVals[j] == null) {
					sqlVals[j] = new StringBuffer();
				}
				if (i > 0) {
					sqlVals[j].append(",");
				}
				sqlVals[j].append(p.getSqlValue(vals[valSize * j + i]));
			}
		}
		if (props.size() > 1) {
			sf.insert(0, "(");
			sf.append(")");
			for (int j = 0; j < valSize; j ++) {
				sqlVals[j].insert(0, "(");
				sqlVals[j].append(")");
			}
		}
		sf.append("in (");
		for (int i = 0; i < valSize; i ++) {
			if (i > 0) {
				sf.append(",");
			}
			sf.append(sqlVals[i]);
		}
		sf.append(")");
		return sf.toString();
	}

	/**
	 * @param props
	 * @param hasTableAlias
	 *            true:(APPS.Id,APPS.NAME),false:(Id,NAME)
	 * @return
	 */
	private static String getPropsString(List<MProperty> props, boolean hasTableAlias) {
		StringBuffer sf = new StringBuffer();
		for (int i = 0; i < props.size(); i++) {
			MProperty p = props.get(i);
			if (i > 0) {
				sf.append(",");
			}
			if (hasTableAlias) {
				sf.append(p.getObject().getNo() + ".");
			}
			sf.append(p.getName());
		}
		if (props.size() > 1) {
			sf.insert(0, "(");
			sf.append(")");
		}
		return sf.toString();
	}
	
	/**
	 * 获取where语句
	 * @param val
	 * @return
	 */
	@JsonIgnore
	public String getSqlCondition(String val) {
		if (WoKit.isEmpty(val)) {
			return null;
		}
		if (isM1() || isM21()) {// 一对一：子对父 或者 多对一
			return MObjectDto.getPropsEqualConditions(object, getProperties(), true, val);
		} else if (is1M()) {// 一对一：父对子
			String subSql = "select " + this.getPropertyIds() + " from " + object.getId() + " where "
					+ MObjectDto.getPropsEqualConditions(object, this.getRelatedObject().getKeyProperties(), false, val);
			return getPropsString(getProperties(), true) + " = (" + subSql + ")";
		} else if (is12M()) {// 一对多
			String subSql = "select " + getPropertyIds() + " from " + object.getId() + " where "
					+ getPropertiesInCondition(getRelatedObject().getKeyProperties(), false, val);
			return getPropsString(getProperties(), true) + " in (" + subSql + ")";
		}
		// 多对多
		String bridgePropStr = this.getRelationIds();
		String subSql = "select " + bridgePropStr + " from " + this.getRelationObjectId() + " where "
				+ getPropertiesInCondition(this.getRelationInverseProperties(), false, val);
		return getPropsString(getProperties(), true) + " in (" + subSql + ")";
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getPropertyIds() {
		return propertyIds;
	}

	public void setPropertyIds(String propertyIds) {
		this.propertyIds = propertyIds;
	}

	public String getPropertyIdsObjectId() {
		return propertyIdsObjectId;
	}

	public void setPropertyIdsObjectId(String propertyIdsObjectId) {
		this.propertyIdsObjectId = propertyIdsObjectId;
	}

	public String getRelatedObjectId() {
		return relatedObjectId;
	}

	public void setRelatedObjectId(String relatedObjectId) {
		this.relatedObjectId = relatedObjectId;
	}

	public String getRelatedName() {
		return relatedName;
	}

	public void setRelatedName(String relatedName) {
		this.relatedName = relatedName;
	}

	public String getRelatedIds() {
		return relatedIds;
	}

	public void setRelatedIds(String relatedIds) {
		this.relatedIds = relatedIds;
	}

	public String getRelationObjectId() {
		return relationObjectId;
	}

	public void setRelationObjectId(String relationObjectId) {
		this.relationObjectId = relationObjectId;
	}

	public String getRelationIds() {
		return relationIds;
	}

	public void setRelationIds(String relationIds) {
		this.relationIds = relationIds;
	}

	public String getRelationInverseIds() {
		return relationInverseIds;
	}

	public void setRelationInverseIds(String relationInverseIds) {
		this.relationInverseIds = relationInverseIds;
	}

	public String getRelationRelatedObjectId() {
		return relationRelatedObjectId;
	}

	public void setRelationRelatedObjectId(String relationRelatedObjectId) {
		this.relationRelatedObjectId = relationRelatedObjectId;
	}

	public Integer getIndex() {
		return index;
	}

	public void setIndex(Integer index) {
		this.index = index;
	}

	public BigMap<String, Object> getData() {
		return data;
	}

	public void setData(BigMap<String, Object> data) {
		this.data = data;
	}

	public MObjectDto getObject() {
		return object;
	}

	public void setObject(MObjectDto object) {
		this.object = object;
	}

	public void setRelatedObject(MObjectDto relatedObject) {
		this.relatedObject = relatedObject;
	}

	public void setRelationObject(MObjectDto relationObject) {
		this.relationObject = relationObject;
	}

	@JsonIgnore
	public Boolean getMetaShow () {
		return data.getBoolean(MObjectDto.PARAM_META_SHOW, false);
	}

	public void setMetaShow (Boolean metaShow) {
		data.put(MObjectDto.PARAM_META_SHOW, metaShow);
	}

	@JsonIgnore
	public String getDefault () {
		return data.getString(PARAM_DEFAULT);
	}

	public void setDefault (String defaultVal) {
		data.put(PARAM_DEFAULT, defaultVal);
	}

	@Override
	public int compareTo(MRelation o) {
		return this.getIndex() - o.getIndex();
	}
}
